//!
/**
 **************************************************************************************************************************************************************
 * 
 **************************************************************************************************************************************************************
 * @file main.cpp
 **************************************************************************************************************************************************************/

#define _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_DEPRECATE


/**************************************************************************************************************************************************************
 * Includes
 **************************************************************************************************************************************************************/
#include <RBMain/RBCommon.h>

#include <RBApplication/RBWin32Application.h>
#include <RBApplication/Main.h>

#include <RBMain/RBCuller.h>
#include <RBD3D9Renderer/RBD3D9Renderer.h>
#include <RBMain/RBDAEReader.h>
#include <RBMain/RBDefaultRenderProcess.h>
#include <RBMain/RBGeometry.h>
#include <RBMain/RBHardwareBufferManager.h>
#include <RBMain/RBMath.h>
#include <RBMain/RBMatrix.h>
#include <RBMain/RBRenderProcess.h>
#include <RBMain/RBRenderTargetGroup.h>
#include <RBMain/RBRenderPassDebug.h>
#include <RBMain/RBSystem.h>
#include <RBMain/RBTexture.h>
#include <RBMain/RBTechnique.h>
#include <RBMain/RBVector.h>
#include <RBMain/RBTechniqueFactory.h>
#include <RBMain/RBVertexElement.h>

#include "RelaxUVS.h"

#define STR(s) #s
#define XSTR(s) STR(s)

#ifdef _DEBUG
#define PATH_TO_RB_LIB "../../../Lib/Debug"
#else
#define PATH_TO_RB_LIB "../../../Lib/Release"
#endif

#define COMMENT_LIB(Library) comment(lib, XSTR(Library##.lib))
#define COMMENT_RBLIB(Library) COMMENT_LIB(PATH_TO_RB_LIB ## "/" ## Library)

#pragma COMMENT_LIB(FCollada)
#pragma COMMENT_RBLIB(RBApplication)
#pragma COMMENT_RBLIB(RBGLRenderer)
#pragma COMMENT_RBLIB(RBD3D9Renderer)
#pragma COMMENT_RBLIB(RBMain)
//#pragma COMMENT_LIB(opengl32)

/**************************************************************************************************************************************************************
 * Defines
 **************************************************************************************************************************************************************/
#define APPLICATION_WIN32 0
#define APPLICATION_WX 1

#define APPLICATION_TYPE APPLICATION_WIN32

#include <fastdelegatebind.h>

/**************************************************************************************************************************************************************
 * Classes implementation
 **************************************************************************************************************************************************************/

class RBPeltTest : public RBWin32Application
{
    RBDeclareRTTI(RBPeltTest, RBWin32Application)
public:

                                RBPeltTest  ();
    virtual                    ~RBPeltTest  ();

    virtual RBRenderProcessPtr  CreateRenderProcess (RBVisibilityManager* pMgr);

    virtual bool                Init                ();
    virtual void                OnIdle              ();

    RBCameraPtr                 m_spShadowCamera;

    bool						PreRenderCallback   (RBRenderProcess* pCurrentProcess, uint32 uiFrameID);

    RBGeometryPtr               CreateRandomGeom    (const RBString& strHeightmap);

    void                        RelaxUVs            ();
    void                        ResetUVs            ();
    void                        ScaleUVs            ();
    void                        UpdateVB            ();

private:

    RBVertexBufferPtr           m_spVertexBuffer;
    VertexTerrain*              m_pVBs[2];
    VertexTerrainNeighbor*      m_pNeighbors;
    uint32                      m_uiCurrentVB;
    uint16                      m_uiVertexCount;

    float32*                    m_idealScalesX;
    float32*                    m_idealScalesY;

};


/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
RBPeltTest::RBPeltTest()
: RBWin32Application(1024,768, false)
, m_uiCurrentVB(0)
, m_uiVertexCount(0)
{
}


/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
RBRenderProcessPtr RBPeltTest::CreateRenderProcess(RBVisibilityManager* pMgr)
{
    RBRenderProcessPtr spProcess = NULL;
    if (m_spShadowCamera)
    {
        spProcess = new RBDefaultRenderProcess(pMgr, m_spCamera, 
            RBDefaultRenderProcess::ShadowParams(m_spShadowCamera, 256, 256));
    }
    else
    {
        spProcess = new RBDefaultRenderProcess(pMgr, m_spCamera);
    }

	//spProcess->AppendPass(new RBRenderPassDebug);
	spProcess->SetPreRenderCallback(RBFastDelegateBind(&RBPeltTest::PreRenderCallback, this));

    return spProcess;
}

/***********************************************************************************************************
 *
 ***********************************************************************************************************/
RBPeltTest::~RBPeltTest()
{
    m_spShadowCamera = 0;
}

/***********************************************************************************************************
 *
 ***********************************************************************************************************/
bool RBPeltTest::PreRenderCallback( RBRenderProcess* /*pCurrentProcess*/, uint32 /*uiFrameID*/ )
{
	RBRenderPassDebug* pDebug = (RBRenderPassDebug*)m_spRenderProcess->GetPassByName("DebugPass");
	if (pDebug)
	{
        pDebug->Render3DLine(float3(0,0,0), float3(1,0,0), RBColor(1,0,0));
        pDebug->Render3DLine(float3(0,0,0), float3(0,1,0), RBColor(0,1,0));
        pDebug->Render3DLine(float3(0,0,0), float3(0,0,1), RBColor(0,0,1));
	}
	return true;
}

void AddNeighbor(VertexTerrainNeighbor& vertex, bool bCond, uint16 idx, uint16 neighbor1, uint16 neighbor2)
{   
    if (bCond)
    {
        vertex.Neighbors[vertex.NeighborCount] = idx;
        vertex.TriangleNeighbor1[vertex.NeighborCount] = neighbor1;
        vertex.TriangleNeighbor2[vertex.NeighborCount] = neighbor2;
        ++vertex.NeighborCount;
    }
}

void ComputeWeights(int32 uiVertexCount, const VertexTerrain* pVBs, VertexTerrainNeighbor* pNeighbors, bool bUseChordal) 
{
    float32 weights[g_uiNeighborCount];
    for (int32 y=1; y < uiVertexCount-1; ++y)
    {
        for (int32 x=1; x < uiVertexCount-1; ++x)
        {
            uint16 vertexIdx = uint16(x + uiVertexCount*y);
            float32 totalWeight = 0;
            for (uint32 neighbor=0; neighbor<pNeighbors[vertexIdx].NeighborCount; ++neighbor)
            {
                if (bUseChordal)
                {
                    weights[neighbor] = 1.0f / (pVBs[vertexIdx].Pos - pVBs[pNeighbors[vertexIdx].Neighbors[neighbor]].Pos).GetLength();
                    totalWeight += weights[neighbor];
                }
                else
                {
                    uint16 j = pNeighbors[vertexIdx].Neighbors[neighbor];
                    uint16 k = pNeighbors[vertexIdx].TriangleNeighbor1[neighbor];
                    uint16 l = pNeighbors[vertexIdx].TriangleNeighbor2[neighbor];
                    // pi and pj form a line and pk and pl connect to this line to make triangles
                    const VertexTerrain& pi = pVBs[vertexIdx];
                    const VertexTerrain& pj = pVBs[j];
                    const VertexTerrain& pk = pVBs[k];
                    const VertexTerrain& pl = pVBs[l];

                    float3 pij = (pi.Pos - pj.Pos);
                    float3 pik = (pi.Pos - pk.Pos);
                    float3 pil = (pi.Pos - pl.Pos);
                    float32 rij = pij.GetLength();

                    pij = pij * (1.0f / rij);
                    pik = pik * (1.0f / pik.GetLength());
                    pil = pil * (1.0f / pil.GetLength());

                    float32 cos2A = pij.Dot(pik);
                    float32 sin2A = pij.Cross(pik).GetLength();
                    float32 tanA = sin2A / (1.0f + cos2A);

                    float32 cos2B = pij.Dot(pil);
                    float32 sin2B = pij.Cross(pil).GetLength();
                    float32 tanB = sin2B / (1.0f + cos2B);

                    weights[neighbor] = fabs(tanA + tanB) / rij;
                    totalWeight += weights[neighbor];
                }
            }

            for (uint32 neighbor=0; neighbor<pNeighbors[vertexIdx].NeighborCount; ++neighbor)
            {
                pNeighbors[vertexIdx].Weights[neighbor] = weights[neighbor] / totalWeight;
            }
        }
    }
}

RBGeometryPtr RBPeltTest::CreateRandomGeom(const RBString& strHeightmap)
{
    IDirect3DTexture9* pD3DTex = 0;
    D3DXIMAGE_INFO info;
    D3DXCreateTextureFromFileEx(RBD3D9Renderer::GetSingleton()->GetD3DDevice(), strHeightmap.c_str(), D3DX_DEFAULT,
                                D3DX_DEFAULT, D3DX_DEFAULT, 0, D3DFMT_X8R8G8B8, D3DPOOL_SYSTEMMEM, 
                                D3DX_FILTER_NONE,D3DX_FILTER_NONE, 0, &info, 0, &pD3DTex);

    const float fXYMultiplier = 20.0f / info.Height;

    m_uiVertexCount = (uint16)info.Width;
    const uint32 triangleCount = (m_uiVertexCount-1)*(m_uiVertexCount-1)*2;
    m_pVBs[0] = new VertexTerrain[m_uiVertexCount*m_uiVertexCount];
    m_pVBs[1] = new VertexTerrain[m_uiVertexCount*m_uiVertexCount];
    m_pNeighbors = new VertexTerrainNeighbor[m_uiVertexCount*m_uiVertexCount];
    D3DLOCKED_RECT rect;
    pD3DTex->LockRect(0, &rect, 0, D3DLOCK_READONLY);
    uint8* pData = (uint8*)rect.pBits;

    m_idealScalesX = new float32[m_uiVertexCount];
    m_idealScalesY = new float32[m_uiVertexCount];

    uint16 last = m_uiVertexCount-1;
    for (int32 y=0; y < m_uiVertexCount; ++y)
    {
        uint8* pCurData = pData;
        for (int32 x=0; x < m_uiVertexCount; ++x)
        {
            uint16 vertexIdx = uint16(x + m_uiVertexCount*y);
            m_pVBs[0][vertexIdx].Pos = float3((x-m_uiVertexCount/2)*fXYMultiplier, (y-m_uiVertexCount/2)*fXYMultiplier, (*pCurData) * 10.0f / 255.0f);
            m_pVBs[0][vertexIdx].UVs = float2(g_uvMultiplier*m_pVBs[0][vertexIdx].Pos(0), g_uvMultiplier*m_pVBs[0][vertexIdx].Pos(1));
            m_pNeighbors[vertexIdx].NeighborCount=0;
            AddNeighbor(m_pNeighbors[vertexIdx], x>0,               vertexIdx-1,                 vertexIdx-1+m_uiVertexCount, vertexIdx-m_uiVertexCount);
            AddNeighbor(m_pNeighbors[vertexIdx], y>0,               vertexIdx-m_uiVertexCount,   vertexIdx-1,                 vertexIdx-m_uiVertexCount+1);
            AddNeighbor(m_pNeighbors[vertexIdx], x<last,            vertexIdx+1,                 vertexIdx+m_uiVertexCount,   vertexIdx-m_uiVertexCount+1);
            AddNeighbor(m_pNeighbors[vertexIdx], y<last,            vertexIdx+m_uiVertexCount,   vertexIdx+1,                 vertexIdx+m_uiVertexCount-1);
            AddNeighbor(m_pNeighbors[vertexIdx], x<last && y>0,     vertexIdx-m_uiVertexCount+1, vertexIdx+1,                 vertexIdx-m_uiVertexCount);
            AddNeighbor(m_pNeighbors[vertexIdx], y<last && x>0,     vertexIdx+m_uiVertexCount-1, vertexIdx+m_uiVertexCount,   vertexIdx-1);
            AddNeighbor(m_pNeighbors[vertexIdx], x<last && y<last,  vertexIdx+m_uiVertexCount+1, vertexIdx+1,                 vertexIdx+m_uiVertexCount);
            AddNeighbor(m_pNeighbors[vertexIdx], x>0 && y>0,        vertexIdx-m_uiVertexCount-1, vertexIdx-1,                 vertexIdx-m_uiVertexCount);

            pCurData += 4;
        }
        pData += rect.Pitch;
    }
    pD3DTex->UnlockRect(0);
    pD3DTex->Release();

    for (int32 y=0; y < m_uiVertexCount; ++y)
    {
        m_idealScalesX[y] = 0;
        for (int32 x=1; x < m_uiVertexCount; ++x)
        {
            uint16 vertexIdx = uint16(x + m_uiVertexCount*y);
            float3 diff = m_pVBs[0][vertexIdx].Pos - m_pVBs[0][vertexIdx-1].Pos;
            m_idealScalesX[y] += g_uvMultiplier * diff.GetLength();
        }
    }

    for (int32 x=0; x < m_uiVertexCount; ++x)
    {
        m_idealScalesY[x] = 0;
        for (int32 y=1; y < m_uiVertexCount; ++y)
        {
            uint16 vertexIdx = uint16(x + m_uiVertexCount*y);
            float3 diff = m_pVBs[0][vertexIdx].Pos - m_pVBs[0][vertexIdx-m_uiVertexCount].Pos;
            m_idealScalesY[x] += g_uvMultiplier * diff.GetLength();
        }
    }

    ComputeWeights(m_uiVertexCount, m_pVBs[0], m_pNeighbors, g_useChordal);

    memcpy(m_pVBs[1], m_pVBs[0], m_uiVertexCount*m_uiVertexCount*sizeof(VertexTerrain));

    m_spVertexBuffer = RBRenderer::GetSingleton()->GetHBMgr()->CreateVertexBuffer(RBHardwareBuffer::HBU_CPUWRITE, sizeof(VertexTerrain), m_uiVertexCount*m_uiVertexCount, m_pVBs[0]);

    RBVertexElement aElts[] = { RBVertexElement(),
                                RBVertexElement(0, RBVertexElement::VET_FLOAT2, RBVertexElement::VEU_TEXCOORD) };

    RBVertexDeclaration* pVDecl = RBRenderer::GetSingleton()->GetHBMgr()->CreateVertexDeclaration(aElts, 2);

    uint16* indices = new uint16[triangleCount*3];
    uint32 curIdx=0;
    for (uint16 y=0; y < m_uiVertexCount-1; ++y)
    {
        for (uint16 x=0; x < m_uiVertexCount-1; ++x)
        {
            uint16 curVertex = x + m_uiVertexCount*y;
            indices[curIdx++] = curVertex;
            indices[curIdx++] = curVertex+1;
            indices[curIdx++] = curVertex+m_uiVertexCount;
            indices[curIdx++] = curVertex+1;
            indices[curIdx++] = curVertex+1+m_uiVertexCount;
            indices[curIdx++] = curVertex+m_uiVertexCount;
        }
    }
    RBIndexBuffer* pIB = RBRenderer::GetSingleton()->GetHBMgr()->CreateIndexBuffer(RBHardwareBuffer::HBU_DEFAULT, RBIndexBufferType::IT_16BIT, triangleCount*3, indices);

    delete[] indices;

    RBGeometry* pGeom = RBGeometry::CreateGeometry(m_spVertexBuffer, pVDecl, pIB, RBGeometryPrimitiveType::TRIANGLELIST);
    pGeom->GetMaterial()->GetSampler(RBMaterial::ST_DIFFUSE).Texture = RBRenderer::GetSingleton()->GetHBMgr()->CreateTexture("Data/Checker.tga");
    return pGeom;
}


/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
bool RBPeltTest::Init()
{
    if (!Super::Init())
    {
        return false;
    }

    m_spSceneRoot = new RBSceneNode;
    RBGeometryPtr spGeom = CreateRandomGeom("Data/Heightmap.tga");

    RBGeometry* pUVsGeometry = (RBGeometry*)spGeom->Clone();
    pUVsGeometry->LocalXForm.Translate = float3(25,0,0);
    pUVsGeometry->SetTechnique(RBTechniqueFactory::GetSingleton()->Retrieve("PeltUVs"));
    m_spSceneRoot->AddChild(spGeom);
    m_spSceneRoot->AddChild(pUVsGeometry);
    m_spSceneRoot->UpdateTransformTree();

    OnSceneLoaded(m_spSceneRoot);

    return true;
}

void ScaleUVs(VertexTerrain* output, uint16 vertexCount, float32* idealScalesX, float32* idealScalesY)
{
    uint16 last = vertexCount-1;
    for (uint16 y=0; y < vertexCount; ++y)
    {
        float2 firstUV = output[y*vertexCount].UVs;
        float2 lastUV = output[y*vertexCount+last].UVs;
        float currentDiff = lastUV.x - firstUV.x;
        float scaleFactor =  idealScalesX[y] / currentDiff;//min(1000.01f,idealScalesX[y] / currentDiff);
        for (uint16 x=0; x < vertexCount; ++x)
        {
            if (!(x==0 ||
                x==last ||
                y==last ||
                y==0))
            {
                //continue;
            }


            uint16 ui=x+vertexCount*y;
            output[ui].UVs.x *= scaleFactor;
        }
    }

    for (uint16 x=0; x < vertexCount; ++x)
    {
        float2 firstUV = output[x].UVs;
        float2 lastUV = output[last*vertexCount+x].UVs;
        float currentDiff = lastUV.y - firstUV.y;
        float scaleFactor =  min(1.01f,idealScalesY[x] / currentDiff);
        for (uint16 y=0; y < vertexCount; ++y)
        {
            if (!(x==0 ||
                x==last ||
                y==last ||
                y==0))
            {
                //continue;
            }


            uint16 ui=x+vertexCount*y;
            output[ui].UVs.y *= scaleFactor;
        }
    }

}

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
void RBPeltTest::RelaxUVs()
{
    uint16 nextVB = (m_uiCurrentVB+1)%2;
    UVRelaxer::RelaxUVs(m_pVBs[m_uiCurrentVB], m_pVBs[nextVB], m_uiVertexCount, m_pNeighbors);
}

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
void RBPeltTest::ScaleUVs()
{
    uint16 nextVB = (m_uiCurrentVB+1)%2;
    ::ScaleUVs(m_pVBs[nextVB], m_uiVertexCount, m_idealScalesX, m_idealScalesY);
}


/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
void RBPeltTest::OnIdle()
{
    if (RBInput::GetSingleton()->IsDown(RBInput::IK_A))
    {
        ResetUVs();
    }

    if (RBInput::GetSingleton()->IsDown(RBInput::IK_K))
    {
        RelaxUVs();
    }


    if (RBInput::GetSingleton()->IsDown(RBInput::IK_L))
    {
        ScaleUVs();
    }

    if (RBInput::GetSingleton()->IsPressed(RBInput::IK_M))
    {
        g_useChordal = !g_useChordal;
        ComputeWeights(m_uiVertexCount, m_pVBs[m_uiCurrentVB], m_pNeighbors, g_useChordal);
    }

    UpdateVB();

    Super::OnIdle();
}

void RBPeltTest::ResetUVs()
{
    uint16 nextVB = (m_uiCurrentVB+1)%2;
    for (int32 y=0; y < m_uiVertexCount; ++y)
    {
        for (int32 x=0; x < m_uiVertexCount; ++x)
        {
            uint16 vertexIdx = uint16(x + m_uiVertexCount*y);
            m_pVBs[nextVB][vertexIdx].UVs = float2(g_uvMultiplier*m_pVBs[m_uiCurrentVB][vertexIdx].Pos(0), g_uvMultiplier*m_pVBs[m_uiCurrentVB][vertexIdx].Pos(1));
        }
    }
}

void RBPeltTest::UpdateVB()
{
    uint16 nextVB = (m_uiCurrentVB+1)%2;
    void* pMappedBuffer = m_spVertexBuffer->Map(RBHardwareBuffer::HBM_WRITE_DISCARD);
    if (pMappedBuffer)
    {
        memcpy(pMappedBuffer, m_pVBs[nextVB], m_uiVertexCount*m_uiVertexCount*sizeof(VertexTerrain));
    }
    m_spVertexBuffer->Unmap();
    m_uiCurrentVB = nextVB;
}

MAIN_WIN32(RBPeltTest)

/**************************************************************************************************************************************************************/
